/*******************************************************************************
 * Copyright (c) 2000, 2005 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package edu.mse.camel.client.uml.collab.outlineview;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import org.eclipse.draw2d.FigureListener;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.KeyEvent;
import org.eclipse.draw2d.KeyListener;
import org.eclipse.draw2d.MouseEvent;
import org.eclipse.draw2d.MouseListener;
import org.eclipse.draw2d.MouseMotionListener;
import org.eclipse.draw2d.Viewport;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.draw2d.parts.Thumbnail;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display;

import edu.mse.camel.client.eventbus.pubsub.EventManager;
import edu.mse.camel.client.eventbus.pubsub.ISubscriber;
import edu.mse.camel.client.events.IEvent;
import edu.mse.camel.client.events.peer.SendGenericPeerEvent;
import edu.mse.camel.client.events.peer.ViewportChangeEvent;

/**
 * A scaled image representation of a Figure.  If the source Figure is not completely 
 * visible, a SelectorFigure is placed over the thumbnail representing the viewable area 
 * and can be dragged around to scroll the source figure.
 */
public class PosterBoardScrollableThumbnail 
extends PosterBoardThumbnail implements ISubscriber {

	private class ClickScrollerAndDragTransferrer
	extends MouseMotionListener.Stub
	implements MouseListener
	{
		private boolean dragTransfer;
		
		public void mouseDoubleClicked(MouseEvent me) {
			
		}
		
		public void mouseDragged(MouseEvent me) {
			if (dragTransfer)
				syncher.mouseDragged(me);
		}
		
		public void mousePressed(MouseEvent me) {
			if (!(PosterBoardScrollableThumbnail.this.getClientArea().contains(me.getLocation())))
				return;
			Dimension selectorCenter = selector.getBounds().getSize().scale(0.5f);
			Point scrollPoint = me.getLocation()
			.getTranslated(getLocation().getNegated())
			.translate(selectorCenter.negate())
			.scale(1.0f / getViewportScaleX(), 1.0f / getViewportScaleY())
			.translate(
					viewport.getHorizontalRangeModel().getMinimum(), 
					viewport.getVerticalRangeModel().getMinimum());
			viewport.setViewLocation(scrollPoint);
			syncher.mousePressed(me);
			dragTransfer = true;
		}
		
		public void mouseReleased(MouseEvent me) {
			syncher.mouseReleased(me);
			dragTransfer = false;
		}
	}

	private class ScrollSynchronizer
	extends MouseMotionListener.Stub
	implements MouseListener
	{
		private Point startLocation;
		private Point viewLocation;

		public void mouseDoubleClicked(MouseEvent me) { }

		public void mouseDragged(MouseEvent me) {
			Dimension d = me.getLocation().getDifference(startLocation);
			d.scale(1.0f / getViewportScaleX(), 1.0f / getViewportScaleY());
			viewport.setViewLocation(viewLocation.getTranslated(d));
			me.consume();
		}

		public void mousePressed(MouseEvent me) {
			startLocation = me.getLocation();
			viewLocation = viewport.getViewLocation();
			me.consume();
		}

		public void mouseReleased(MouseEvent me) {
		}
	}

	public class SelectorFigure extends ViewportFigure	{
		
		public SelectorFigure(RGB rgb) {
			super(rgb);
		}
		
		public void paintFigure(Graphics g) {
			Rectangle bounds = getBounds().getCopy();

			// Avoid drawing images that are 0 in dimension
			if (bounds.width < 5 || bounds.height < 5)
				return;

			// Don't paint the selector figure if the entire source is visible.
			Dimension thumbnailSize = new Dimension(getThumbnailImage());
			// expand to compensate for rounding errors in calculating bounds
			Dimension size = getSize().getExpanded(1, 1); 
			if (size.contains(thumbnailSize))
				return;

			bounds.height--;
			bounds.width--;
			g.drawImage(image, iBounds, bounds);

			g.setForegroundColor(border);
			g.drawRectangle(bounds);
		}

	}
	
	private FigureListener figureListener = new FigureListener() {
		public void figureMoved(IFigure source) {
			reconfigureSelectorBounds();
		}
	};
	
	private KeyListener keyListener = new KeyListener.Stub() {
		public void keyPressed(KeyEvent ke) {
			int moveX = viewport.getClientArea().width / 4; 
			int moveY = viewport.getClientArea().height / 4;
			if (ke.keycode == SWT.HOME || (isMirrored() ? ke.keycode == SWT.ARROW_RIGHT
					: ke.keycode == SWT.ARROW_LEFT))
				viewport.setViewLocation(viewport.getViewLocation().translate(-moveX, 0));
			else if (ke.keycode == SWT.END || (isMirrored() ? ke.keycode == SWT.ARROW_LEFT
					: ke.keycode == SWT.ARROW_RIGHT))
				viewport.setViewLocation(viewport.getViewLocation().translate(moveX, 0));
			else if (ke.keycode == SWT.ARROW_UP || ke.keycode == SWT.PAGE_UP)
				viewport.setViewLocation(viewport.getViewLocation().translate(0, -moveY));
			else if (ke.keycode == SWT.ARROW_DOWN  || ke.keycode == SWT.PAGE_DOWN)
				viewport.setViewLocation(viewport.getViewLocation().translate(0, moveY));
		}
	};

	private PropertyChangeListener propListener = new PropertyChangeListener() {
		public void propertyChange(PropertyChangeEvent evt) {
			reconfigureSelectorBounds();
		}
	};

	private SelectorFigure selector;

	private ScrollSynchronizer syncher;
	private Viewport viewport;

	/**
	 * Creates a new ScrollableThumbnail.
	 */
	public PosterBoardScrollableThumbnail(RGB rgb) {
		super();
		initialize(rgb);
	}

	/**
	 * Creates a new ScrollableThumbnail that synchs with the given Viewport.
	 * @param port The Viewport
	 */
	public PosterBoardScrollableThumbnail(Viewport port, RGB rgb) {
		super();
		setViewport(port);
		initialize(rgb);
	}

	/**
	 * @see Thumbnail#deactivate()
	 */
	public void deactivate() {
		viewport.removePropertyChangeListener(Viewport.PROPERTY_VIEW_LOCATION, propListener);
		viewport.removeFigureListener(figureListener);
		remove(selector);
		selector.dispose();
		super.deactivate();
	}

	private double getViewportScaleX() {
		return (double)targetSize.width / viewport.getContents().getBounds().width;
	}

	private double getViewportScaleY() {
		return (double)targetSize.height / viewport.getContents().getBounds().height;
	}

	private void initialize(RGB rgb) {
		selector = new SelectorFigure(rgb);
		selector.addMouseListener(syncher = new ScrollSynchronizer());
		selector.addMouseMotionListener(syncher);
		selector.setFocusTraversable(true);
		selector.addKeyListener(keyListener);
		add(selector);
		ClickScrollerAndDragTransferrer transferrer = 
			new ClickScrollerAndDragTransferrer();
		addMouseListener(transferrer);
		addMouseMotionListener(transferrer);
	}

	private static Rectangle oldBounds = null;
	
	private void reconfigureSelectorBounds() {
		Rectangle rect = new Rectangle();
		Point offset = viewport.getViewLocation();
		offset.x -= viewport.getHorizontalRangeModel().getMinimum();
		offset.y -= viewport.getVerticalRangeModel().getMinimum();
		rect.setLocation(offset);
		rect.setSize(viewport.getClientArea().getSize());
		rect.scale(getViewportScaleX(), getViewportScaleY());
		rect.translate(getClientArea().getLocation());
		selector.setBounds(rect);
		
		if (changeWorthyOfTransmitting(rect)) {
			//Debug.log_info(CollabPlugin.PLUGIN_ID, "Event sent");
			ViewportChangeEvent event = new ViewportChangeEvent();
			event.x = rect.x;
			event.y = rect.y;
			event.width = rect.width;
			event.height = rect.height;
			SendGenericPeerEvent genericEvent = new SendGenericPeerEvent();
			genericEvent.writeObject(event);
			EventManager.getInstance().dispatchEventAsynchronous(genericEvent);
		}
	}

	private boolean changeWorthyOfTransmitting(Rectangle rect) {
		if (oldBounds == null) {
			oldBounds = rect;
			return true;
		}
		if (Math.abs(oldBounds.x - rect.x) > 20 ||
				Math.abs(oldBounds.y - rect.y) > 20 ||
				Math.abs(oldBounds.height - rect.height) > 20 ||
				Math.abs(oldBounds.width - rect.width) > 20) {
			oldBounds = rect;
			return true;
		}
		return false;
	}

	/**
	 * Reconfigures the SelectorFigure's bounds if the scales have changed.
	 * @param scaleX The X scale
	 * @param scaleY The Y scale
	 * @see org.eclipse.draw2d.parts.Thumbnail#setScales(float, float)
	 */
	protected void setScales(float scaleX, float scaleY) {
		if (scaleX == getScaleX() && scaleY == getScaleY())
			return;

		super.setScales(scaleX, scaleY);
		reconfigureSelectorBounds();
	}

	/**
	 * Sets the Viewport that this ScrollableThumbnail will synch with.
	 * @param port The Viewport
	 */
	public void setViewport(Viewport port) {
		port.addPropertyChangeListener(Viewport.PROPERTY_VIEW_LOCATION, propListener);
		port.addFigureListener(figureListener);
		viewport = port;
	}
	
	public void addPropertyChangeListnerToViewport(PropertyChangeListener propListener) {
		viewport.addPropertyChangeListener(propListener);
	}

	@Override
	public void notifyEvent(IEvent e) {
		if (e instanceof ViewportChangeEvent) {
			ViewportChangeEvent vce = (ViewportChangeEvent) e;
			final Rectangle rectangle = new Rectangle(vce.x, vce.y, vce.width, vce.height);
			Display.getDefault().asyncExec(new Runnable() {
			
				@Override
				public void run() {
					selector.setBounds(rectangle);			
			
				}
			});
		}
	}
	
	public void setFigureColor(RGB rgb) {
		selector.setFigureColor(rgb);
	}
	

}
